/**
 * 
 */
package org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.javahispano.javacup.modelo.Comando;
import org.javahispano.javacup.modelo.Constantes;
import org.javahispano.javacup.modelo.JugadorDetalle;
import org.javahispano.javacup.modelo.Posicion;
import org.javahispano.javacup.modelo.SituacionPartido;
import org.javahispano.javacup.modelo.Tactica;
import org.javahispano.javacup.modelo.TacticaDetalle;

import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.Player.PLAYER_ROLE;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.fsm.StateMachine;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.fsm.state.Defending;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.fsm.state.Wait;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.geometrics.Matrix2D;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.geometrics.Segment2D;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.geometrics.Vector2D;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.messages.MessageDispatcher;
import org.javahispano.javacup.tacticas_aceptadas.Pescaditos.com.chidotronics.javacup.messages.Telegram;

/**
 * @author Carlos Alberto
 *
 */
public class TacticaPescaditos implements Tactica{

	private TacticaDetalle detalle;

	private List<Player<?>> players ;

	private Ball ball;

	private StateMachine<TacticaPescaditos> stateMachine;

	public final static int NUM_PLAYERS = 11;
	
	private static final int MIN_PASS_DISTANCE_SQ = 81;

	private static final int SHOOT_ATTEMPS = 3;

	
  	private double halfWidth = Constantes.ANCHO_CAMPO_JUEGO / 2;
  	private double height = Constantes.LARGO_CAMPO_JUEGO / 2;
  	private static final double HOT_LINE = 30;

	private MessageDispatcher dispatcher;

	private GoalKeeper goalKeeper;


	

	public TacticaPescaditos() {
		ball = new Ball();
		dispatcher = new MessageDispatcher(this);
		
		homeGoal = new Vector2D(Constantes.centroArcoInf.getX(),Constantes.centroArcoInf.getY());
		
		goalSegment = new Segment2D(-Constantes.LARGO_ARCO/2,-height+1,Constantes.LARGO_ARCO/2,-height+1);

		opponentsGoal = new Vector2D(Constantes.centroArcoSup.getX(),Constantes.centroArcoSup.getY());
		initializeHomePositions();		
		initializePlayers();
		
		
		
		stateMachine = new StateMachine<TacticaPescaditos>(this);
		stateMachine.setCurrentState(Defending.instance);
		stateMachine.setGlobalState(null);
		
		supportSpotCalc = new SupportSpotCalculator(this);

	}



	private void initializeHomePositions() {
		
	    alineacion1=new Posicion[]{
	            new Posicion(0.0,-52.0),
	            new Posicion(0.0,-32.0),
	            new Posicion(-20.6667,-21.0),
	            new Posicion(0.0,-21.0),
	            new Posicion(20.6667,-21.0),
	            new Posicion(20.6667,-10.5),
	            new Posicion(0.0,-10.5),
	            new Posicion(-20.6667,-10.5),
	            new Posicion(-20.6667,-0.01),
	            new Posicion(0.0,-0.01),
	            new Posicion(20.6667,-0.01)
	        };

	       alineacion2=new Posicion[]{
	            new Posicion(0.0,-52.0),
	            new Posicion(0.0,-32.0),
	            new Posicion(-20.6667,-21.0),
	            new Posicion(0.0,-21.0),
	            new Posicion(20.6667,-21.0),
	            new Posicion(20.6667,-10.5),
	            new Posicion(5.230769230769231,-7.601809954751132),
	            new Posicion(-20.6667,-10.5),
	            new Posicion(-20.6667,-0.01),
	            new Posicion(-5.230769230769231,-7.601809954751132),
	            new Posicion(20.6667,-0.01)
	        };
		
	   


	}	            


	private void initializePlayers() {
		detalle = new TacticaDetalleImpl();    	
		JugadorDetalle[] details = detalle.getJugadores();


		players = new ArrayList<Player<?>>(NUM_PLAYERS);

		opponents = new Vector2D[NUM_PLAYERS];

		goalKeeper = new GoalKeeper(this,details[0]);

		players.add(goalKeeper);
		Player<?> player;

		for (int i = 1; i < NUM_PLAYERS; i++){		
			TeamPlayer.PLAYER_ROLE role = (i < 5)? PLAYER_ROLE.DEFENDER: PLAYER_ROLE.ATTACKER;

			player = new TeamPlayer(this,details[i],role);
			players.add(player);
		}



	}




	private Posicion[] alineacion1,alineacion2;

	private Player<?> controllingPlayer;
	private TeamPlayer supportingPlayer;

	private Vector2D[] opponents;

	private Player<?> playerClosestToBall;


	private TeamPlayer receiver;
	

	private Vector2D opponentsGoal ;
	
	private SupportSpotCalculator supportSpotCalc;

	private Vector2D homeGoal;



	private int currentIter;

	private boolean kickGoal;

	private Player<?> playerClosestToTrajectory;

	private double distOppClosestToBall;


	private Segment2D goalSegment ;

	private double opponentsScore ;


	public List<Comando> ejecutar(SituacionPartido sp) {
		List<Comando> commands = new LinkedList<Comando>();
		currentIter = sp.iteracion();
		ball.update(sp);
		


		
		updateRivalesPositions(sp.rivales());
		kickGoal = false;
		
		if ( sp.sacaRival() ||   opponentKickedTheBall(sp.iteracionesParaRematarRival()) >= 0  ) {			
				setControllingPlayer(null);
				setReceiver(null);
		} 

		if (sp.saco()) {
			setControllingPlayer(null);
			if (this.receiver != null){
				this.receiver.getStateMachine().changeState(Wait.instance);
				setReceiver(null);
			}
			kickGoal = (ball.getPosition().getY()<= (- height + Constantes.ANCHO_AREA_CHICA));

		} 
		
		updatePlayersPerceptions(sp);
		

		
		calculateClosestPlayerToBall();
		calculateClosestPlayerToTrajectory();
		
		

		stateMachine.update();
		
		dispatcher.dispatchDelayed();
		
		for (Player<?> player:players) {
			player.update();

			
			safeAdd(commands,player.getKickCommand());
			safeAdd(commands,player.getGotoCommand());
			

		}
		return commands;
	}





	private void safeAdd(List<Comando> commands, Comando comando) {
		if (comando != null) {
			commands.add(comando);
		}
		
	}



	private void updateRivalesPositions(Posicion[] rivales) {
		int i = 0;

		double oppClosestToBall = Double.MAX_VALUE;
		for (Posicion pos:rivales) {
			Vector2D vOpp = new Vector2D(pos.getX(),pos.getY());
			this.opponents[i++] = vOpp;
			double dist = vOpp.distanceSq(ball.getPosition());
			if (dist < oppClosestToBall) {
				distOppClosestToBall = oppClosestToBall;
			}
		}
		
	}



	private void updatePlayersPerceptions(SituacionPartido sp) {
		int iterationsToKick[] = sp.iteracionesParaRematar();
		Posicion[] misJugadores = sp.misJugadores();


		for (Player<?> player:players) {

			int index = player.getId();
			int iterations = iterationsToKick[index];			
			Posicion pos = misJugadores[index];
			
			player.setPosition(pos.getX(), pos.getY());
			player.setCanKick(false);
			if ( ballKicked(iterations) ) {
				setControllingPlayer(player);
			}
		}
		
		for (int index:sp.puedenRematar()){
			players.get(index).setCanKick(true);
		}


	}



	public TacticaDetalle getDetalle() {
		return detalle;
	}

	public Posicion[] getPosicionRecive(SituacionPartido sp) {
		setHomePositions(alineacion1);
		setControllingPlayer(null);
		return alineacion1;
	}

	public void setHomePositions(Posicion[] positions) {

		for (Player<?> player:players){
			int index = player.getId();
			Vector2D position = new Vector2D(positions[index].getX(),positions[index].getY());
			player.setHomePosition(position);
			if (player.getStateMachine().getCurrentState().equals(Wait.instance)){
				player.getSteering().setTarget(position);
			}

		}



	}



	public Posicion[] getPosicionSaca(SituacionPartido sp) {
		setHomePositions(alineacion2);
		setControllingPlayer(null);
		return alineacion2;
	}


	public void setControllingPlayer(Player<?> player) {
		if (controllingPlayer != null){
			controllingPlayer.setControlling(false);
		}

		controllingPlayer = player;
		if (controllingPlayer!=null )  {							
			controllingPlayer.setControlling(true);
		}
	}

	private int opponentKickedTheBall(int[] iteraciones) {
		for (int i = 0; i < iteraciones.length;i++){
			if ( ballKicked( iteraciones[i] ) ){
				return i;
			}
		}
		return -1;
	}

	private boolean ballKicked(int i) {
		boolean b = i==Constantes.ITERACIONES_GOLPEAR_BALON;
		if (b){
			ball.predictPosition();			
		}
		return b;
	}



	public boolean isControlling() {
		return controllingPlayer != null;
	}



	/**
	 * @return the stateMachine
	 */
	 public StateMachine<TacticaPescaditos> getStateMachine() {
		return stateMachine;
	}



	/**
	 * @return My players list
	 */
	 public List<Player<?>> getPlayers() {
		return this.players;
	}



	public Ball getBall() {
		return ball;
	}



	public Player<?> getControllingPlayer() {
		return controllingPlayer;
	}

	public static final double PASS_ANGLE = 13;

	public static final double SHOOT_ANGLE = 60;

	private static final double TOLERANCE = 100;


	public void requestPass(TeamPlayer requester) {
		if (!controllingPlayer.canKick()){
			return;
		}
		double distanceSq = controllingPlayer.getPosition().distanceSq(requester.getPosition());
		double power = requester.getKickForce();
		if ( distanceSq < controllingPlayer.getMaxPassRangeSq() &&
				isPassSafeFromAllOpponents(controllingPlayer.getPosition(),
				requester.getPosition(),
				requester,
				power,
				
				PASS_ANGLE)){

			dispatcher.dispatch(requester.getId(), 
					controllingPlayer.getId(), 
					Telegram.MessageType.PASS_TO_ME,
					requester,
					0);

		}
	}



	boolean isPassSafeFromAllOpponents(Vector2D from,
			Vector2D target, Player<?> receiver, double kickForce,  double vAngle) {

		Vector2D toTargetNormalized = target.substract(from).normalized();

		Matrix2D transMat = Matrix2D.buildToLocalSpaceMatrix( toTargetNormalized,
										toTargetNormalized.perp(),
											from);

		for (Vector2D opponent:opponents) {
			if (!isPassSafeFromOpponent(from, 
					target, 
					receiver, 
					opponent, 
					transMat.toVector2D(opponent),
					kickForce,vAngle)) {
				return false;
			}
		}

		return true;
	}



	private boolean isPassSafeFromOpponent(Vector2D from, Vector2D target,
			Player<?> receiver, Vector2D opp, Vector2D localPosOpp, double kickForce,double vAngle) {

		if ( localPosOpp.y < 0 ) {     
			return true;
		}

		double distOppFrom = opp.distanceSq(from) - TOLERANCE;
		if (from.distanceSq(target) <  distOppFrom) {
			opponentsScore --;
			if (receiver != null) {
				if ( (target.distanceSq(opp)- TOLERANCE)  > 
				target.distanceSq(receiver.getPosition()) ){
					return true;
				}
				


				return false;

			} else	{
				return true;
			} 

		}
		double maxSpeed = Constantes.VELOCIDAD_MAX;
		
		opponentsScore --;

		double timeForBall = ball.timeToCoverDistance(new Vector2D(0,0),
				new Vector2D(0, localPosOpp.y),
				kickForce,vAngle);

		double reach = maxSpeed * timeForBall+ Constantes.DISTANCIA_CONTROL_BALON_PORTERO+15+TOLERANCE;


		if ( Math.abs(localPosOpp.x) < reach )
		{
			return false;
		}

		return true;
	}
	


	private void calculateClosestPlayerToTrajectory(){
		double closestToTrajectory = Double.MAX_VALUE;
		playerClosestToTrajectory = null;
		for (Player<?> player:players){
			

			double distToTrajectory = ball.getTrajectory().ptSegDistSq(player.getPosition());	
			player.setDistSqToTrajectory(distToTrajectory);

			if (player != playerClosestToBall &&distToTrajectory < closestToTrajectory){
				closestToTrajectory = distToTrajectory;
				playerClosestToTrajectory = player;
			}
		}
	}


	private void calculateClosestPlayerToBall()
	{
		double closestToBall = Double.MAX_VALUE;
		

		for (Player<?> player:players)
		{

			double dist = player.getPosition().distanceSq(ball.getPosition()) ;
			

			player.setDistSqToBall(dist);

			if (dist < closestToBall) {
				closestToBall = dist;
				playerClosestToBall = player;
			}
			

		}

	}


	public Player<?> getPlayerClosestToBall() {
		return playerClosestToBall;
	}



	public TeamPlayer getReceiver() {
		return receiver;
	}



	public Vector2D getGoalTarget(Vector2D ballPos, double power, double vAngle) {



		Vector2D ballTarget = new Vector2D(0,52.5).substract(ballPos) ;
		ballTarget = ballTarget.multiply(1.5);
		

			double time = ball.timeToCoverDistance(ballPos,
					ballTarget,
					power,vAngle);

			if (time >= 0 &&  isPassSafeFromAllOpponents(ballPos, ballTarget, null, power, vAngle) ){
				return ballTarget;
			}

		return null;
	}



	/**
	 * @return the opponents
	 */
	public Vector2D[] getOpponentPositions() {
		return opponents;
	}



	/**
	 * @return the supportingPlayer
	 */
	public TeamPlayer getSupportingPlayer() {
		return supportingPlayer;
	}



	/**
	 * @param supportingPlayer the supportingPlayer to set
	 */
	public void setSupportingPlayer(TeamPlayer supportingPlayer) {
		this.supportingPlayer = supportingPlayer;
	}



	public TeamPlayer determineBestSupportingAttacker() {
		  double closest = Double.MAX_VALUE;

		  TeamPlayer bestPlayer = null;

		  for (Player<?> player:players){

		    if ( TeamPlayer.PLAYER_ROLE.ATTACKER.equals( player.getPlayerRole())  && 
		    	player != controllingPlayer ) {
		    	
		    	

		      double dist = player.getPosition().distanceSq(supportSpotCalc.getBestSupportingSpot());
		    
		      if ((dist < closest) )
		      {
		        closest = dist;

		        bestPlayer = (TeamPlayer) player;
		      }
		    }
		  }

		  return bestPlayer;
	}



	public Player<?> findPass(Player<?> passer, Vector2D passTarget,
			double power, double vAngle) {

		  Player<?> receiver = null;
		  
		  double    closest = Double.MAX_VALUE;
		  Vector2D target;
		  double score = 0;
		  double bestScore =0;
		  opponentsScore = 0;
		  double closestScore = 0;
		  for (int index = 10; index >0;index--){   
			  Player<?> curPlyr =players.get(index);
			  if ( curPlyr != passer ) {  
				  double distanceSq = passer.getPosition().distanceSq(curPlyr.getPosition());
				  
				  if (  distanceSq >  MIN_PASS_DISTANCE_SQ && distanceSq < passer.getMaxPassRangeSq()){

					  target = new Vector2D();
					  if (canPassToReceiver(passer, curPlyr,target,  power,vAngle)) {

						  double dist2Goal = Math.abs(target.y - opponentsGoal.y );

						  if (dist2Goal < closest) {
							  closest = dist2Goal;
							  closestScore += 2;
							  score = 11+closestScore;
						  } 
						  
						  
					  }
					  score -=opponentsScore;
					 if (score > bestScore){
						 bestScore = score;
						  receiver = curPlyr;
						  passTarget.setLocation( target );	 
					 } 
					  
				  }
			  }
		  }

		
		return receiver;
	}



	private boolean canPassToReceiver(Player<?> passer,
			Player<?> receiver,Vector2D passTarget, double power, double vAngle) {


		double time = ball.timeToCoverDistance(ball.getPosition(), receiver.getPosition(), power,vAngle);

		if (time < 0) {
			return false;
		}

		double closest = Double.MAX_VALUE;



		double dist = Math.abs(receiver.getPosition().y - opponentsGoal.y);

		if (( dist < closest) &&
				isPassSafeFromAllOpponents(ball.getPosition(),
						receiver.getPosition(),
						receiver,
						power,
						vAngle)) {
			closest = dist;
			passTarget.setLocation( receiver.getPosition());
			return true;

		}


		return false;
	}






	public boolean isInsidePlayingArea(Vector2D p) {

        return (Math.abs(p.getX())  < halfWidth &&
                Math.abs(p.getY())   < height);
	}



	public double getHeightPitch() {
		return height;
	}



	public Vector2D getHomeGoal() {

		return homeGoal;
	}






	public Vector2D getSupportSpot() {
		return supportSpotCalc.getBestSupportingSpot();
	}



	/**
	 * @param receiver the receiver to set
	 */
	public void setReceiver(TeamPlayer receiver) {
		this.receiver = receiver;
	}



	public boolean isOpponentWithinRadius(Vector2D position, double radius) {
		for (Vector2D pos:opponents){
			if (position.distanceSq(pos) < radius) {
				return true;
			}
		}
		return false;
	}



	/**
	 * @return the supportSpotCalc
	 */
	public SupportSpotCalculator getSupportSpotCalc() {
		return supportSpotCalc;
	}


	/**
	 * @return the dispatcher
	 */
	public MessageDispatcher getDispatcher() {
		return dispatcher;
	}



	public int getIteration() {
		
		return currentIter;
	}



	/**
	 * @return the halfWidth
	 */
	public double getHalfWidth() {
		return halfWidth;
	}



	/**
	 * @return the kickGoal
	 */
	public boolean isKickGoal() {
		return kickGoal;
	}



	public void determineBestSupportingPosition() {
		supportSpotCalc.determineBestSupportingPosition();
		
	}



	/**
	 * @return the playerClosestToTrajectory
	 */
	public Player<?> getPlayerClosestToTrajectory() {
		return playerClosestToTrajectory;
	}



	/**
	 * @return the distOppClosestToBall
	 */
	public double getDistOppClosestToBall() {
		return distOppClosestToBall;
	}



	/**
	 * @return the goalSegment
	 */
	public Segment2D getGoalSegment() {
		return goalSegment;
	}


}
